﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using FunctionalLayer;
using ControlLayer;
using System.IO;
using System.Globalization;
using System.Management;

namespace SEMAuto
{
    public enum ConfigType
    {
        Sensor,
        Logfile,
        XBee,
        Arduino,
        Global
    }

    public enum VarType
    {
        TimeSpan,
        Int,
        Double,
        String
    }

    public partial class ConfigurationForm : Form
    {
        #region Fields
        private List<string> _errorList;

        private bool readyToBeInitiated;
        public bool ReadyToBeInitiated
        {
            get { return readyToBeInitiated; }
            set { readyToBeInitiated = value; }
        }
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public ConfigurationForm()
        {
            InitializeComponent();
            _errorList = new List<string>();
            SetSettings();
        }

        /// <summary>
        /// Set config settings to the configuration form
        /// </summary>
        public void SetSettings()
        {
            //ConfigSingleton.Settings.ArduinoPort = getComPort("Arduino Uno");
            //ConfigSingleton.Settings.XBeePort = getComPort("Prolific USB-to-Serial Comm Port");

            string totalRacetime;
            #region Create total racetime string
            int seconds = ConfigSingleton.Settings.MaxRaceTime.Seconds;
            int minutes = ConfigSingleton.Settings.MaxRaceTime.Minutes + (ConfigSingleton.Settings.MaxRaceTime.Hours * 60);
            totalRacetime = minutes + ":" + seconds;
            #endregion
            textBoxTotalRaceTime.Text = totalRacetime;
            textBoxLaps.Text = ConfigSingleton.Settings.Laps.ToString();
            textBoxSectionsPerLap.Text = ConfigSingleton.Settings.SectionsPerLap.ToString();
            textBoxLapDistance.Text = ConfigSingleton.Settings.LapDistance.ToString();

            textBoxWheelCircumference.Text = (ConfigSingleton.Settings.WheelCircumference * 1000).ToString();

            textBoxLogfilePath.Text = ConfigSingleton.Settings.LogFilePath;
            textBoxLogfilePathError.Text = ConfigSingleton.Settings.LogFilePathError;

            buttonArduinoRefresh_Click(null, EventArgs.Empty);
            buttonXBeeRefresh_Click(null, EventArgs.Empty);

            // Check if XBee port and Arduino port settings available and COM ports loaded into the combobox, else skip


            if (ConfigSingleton.Settings.ArduinoPort != null && comboBoxArduinoPort.Items.Count > 0)
            {
                if (comboBoxArduinoPort.Items.Contains(ConfigSingleton.Settings.ArduinoPort))
                {
                    comboBoxArduinoPort.SelectedIndex = comboBoxArduinoPort.FindStringExact(ConfigSingleton.Settings.ArduinoPort);
                }
            }
            if (ConfigSingleton.Settings.XBeePort != null && comboBoxXBeePort.Items.Count > 0)
            {
                if (comboBoxXBeePort.Items.Contains(ConfigSingleton.Settings.XBeePort))
                {
                    comboBoxXBeePort.SelectedIndex = comboBoxXBeePort.FindStringExact(ConfigSingleton.Settings.XBeePort);
                }
            }
        }

        /// <summary>
        /// Switch to a specific tab in the configuration form
        /// </summary>
        /// <param name="configType">Configuration type</param>
        public void SwitchToTab(ConfigType configType)
        {
            switch (configType)
            {
                case ConfigType.Sensor:
                    tabControl1.SelectTab(tPSensor);
                    break;
                case ConfigType.Logfile:
                    tabControl1.SelectTab(tPLogFile);
                    tPLogFile.Select();
                    break;
                case ConfigType.XBee:
                    tabControl1.SelectTab(tPXBee);
                    tPXBee.Select();
                    break;
                case ConfigType.Arduino:
                    tabControl1.SelectTab(tPArduino);
                    tPArduino.Select();
                    break;
                case ConfigType.Global:
                    tabControl1.SelectTab(tPGlobal);
                    tPArduino.Select();
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Apply arduino port setting
        /// </summary>
        private void arduinoApply()
        {
            if (comboBoxArduinoPort.SelectedItem != null && comboBoxXBeePort.SelectedItem != comboBoxArduinoPort.SelectedItem)
            {
                ConfigSingleton.Settings.ArduinoPort = (string)comboBoxArduinoPort.SelectedItem;
            }
            else if (comboBoxArduinoPort.SelectedItem == null)
            {
                _errorList.Add("Incorrect arduino COM port inserted! \r\nValue is empty\r\n\r\n");
                return;
            }
            else
            {
                _errorList.Add("Incorrect arduino COM port inserted! \r\nThe xBee port can't be the same as the arduino Port\r\n\r\n");
                return;
            }
        }

        /// <summary>
        /// Apply Xbee port setting
        /// </summary>
        private void xBeeApply()
        {
            if (comboBoxXBeePort.SelectedItem != null && comboBoxXBeePort.SelectedItem != comboBoxArduinoPort.SelectedItem)
            {
                // Set config settings
                ConfigSingleton.Settings.XBeePort = (string)comboBoxXBeePort.SelectedItem;
            }
            else if (comboBoxXBeePort.SelectedItem == null)
            {
                _errorList.Add("Incorrect XBee COM port inserted! \r\nValue is empty\r\n\r\n");
                return;
            }
            else
            {
                _errorList.Add("Incorrect XBee COM port inserted! \r\nThe xBee port can't be the same as the arduino Port\r\n\r\n");
                return;
            }
        }

        /// <summary>
        /// Apply log file path setting
        /// </summary>
        private void logfileApply()
        {
            string path = textBoxLogfilePath.Text;
            if (path == "")
            {
                _errorList.Add("Incorrect log file path inserted! \r\nValue is empty\r\n\r\n");
                return;
            }

            string pathError = textBoxLogfilePathError.Text;
            if (pathError == "")
            {
                _errorList.Add("Incorrect log file path inserted! \r\nValue is empty\r\n\r\n");
                return;
            }
            // Set config settings
            ConfigSingleton.Settings.LogFilePath = path;
            ConfigSingleton.Settings.LogFilePathError = pathError;
        }

        /// <summary>
        /// Check if correct values have been inserted and save the settings in the config 
        /// </summary>
        private void sensorApply()
        {
            // For global:
            // Check if correct values have been inserted and save the settings in the config 
            double wheelCircumference = 0;
            if (checkInput<double>(textBoxWheelCircumference.Text, VarType.Double, "Incorrect wheel circumference inserted!", labelWheelCircumference, ref wheelCircumference))
            {
                wheelCircumference /= 1000;
                //Set to config
                ConfigSingleton.Settings.WheelCircumference = wheelCircumference;
            }
        }

        /// <summary>
        /// Check if correct values have been inserted and save the settings in the config 
        /// </summary>
        private void globalApply()
        {
            // For global:
            // Check if correct values have been inserted and save the settings in the config 
            TimeSpan totalRaceTime = new TimeSpan();
            if (checkInput<TimeSpan>(textBoxTotalRaceTime.Text, VarType.TimeSpan, "Incorrect total race time inserted!", labelTotalRaceTime, ref totalRaceTime))
            {
                //Set to config
                ConfigSingleton.Settings.MaxRaceTime = totalRaceTime;
            }

            int sectionsPerLap = 0;
            if (checkInput<int>(textBoxSectionsPerLap.Text, VarType.Int, "Incorrect sections per lap inserted!", labelSectionsPerLap, ref sectionsPerLap))
            {
                //Set to config
                ConfigSingleton.Settings.SectionsPerLap = sectionsPerLap;
            }

            int laps = 1;
            if (checkInput<int>(textBoxLaps.Text, VarType.Int, "Incorrect laps inserted!", labelLaps, ref laps))
            {
                //Set to config
                ConfigSingleton.Settings.Laps = laps;
            }

            double lapDistance = 0;
            if (checkInput<double>(textBoxLapDistance.Text, VarType.Double, "Incorrect race distance insterted!", labelLapDistance, ref lapDistance))
            {
                ConfigSingleton.Settings.LapDistance = lapDistance;
            }

        }

        /// <summary>
        /// Check if inserted input is correct
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">input value string</param>
        /// <param name="type">variable type</param>
        /// <param name="errorMessage">errormessage</param>
        /// <param name="displayLabel">label that changes colour when a wrong value is inserted</param>
        /// <param name="output">converted value</param>
        /// <returns>result bool</returns>
        private bool checkInput<T>(string input, VarType type, string errorMessage, Label displayLabel, ref T output)
        {
            object result = null;
            if (input == "")
            {
                displayLabel.ForeColor = Color.Red;
                _errorList.Add(errorMessage + "\r\nValue is empty\r\n\r\n");
                return false;
            }
            try
            {
                switch (type)
                {
                    case VarType.TimeSpan:
                        TimeSpan ts = new TimeSpan();
                        //only minutes and seconds allowed as input
                        string[] splittedInput = input.Split(':');
                        if (splittedInput.Length == 2)
                        {
                            int minutes = Convert.ToInt32(splittedInput[0]);
                            int seconds = Convert.ToInt32(splittedInput[1]);
                            if (seconds > 60 || seconds < 0 || minutes < 0)
                            {
                                throw new InvalidOperationException();
                            }

                            int hours = (int)(minutes / 60);
                            minutes = minutes % 60;
                            ts = new TimeSpan(hours, minutes, seconds);
                            result = ts;
                        }
                        else
                        {
                            throw new InvalidOperationException();
                        }
                        break;
                    case VarType.Int:
                        int i = int.Parse(input);
                        result = i;
                        break;
                    case VarType.Double:
                        double d = double.Parse(input);
                        result = d;
                        break;
                    case VarType.String:
                        result = input;
                        break;
                    default:
                        // Wat gebeurt hier?!
                        break;
                }
            }
            catch (Exception)
            {
                displayLabel.ForeColor = Color.Red;
                _errorList.Add(errorMessage);
                return false;
            }
            displayLabel.ForeColor = Color.Black;
            output = (T)result;
            return true;
        }

        #region Button Event Handlers
        private void buttonArduinoDefault_Click(object sender, EventArgs e)
        {
            buttonArduinoRefresh_Click(null, EventArgs.Empty);
            if (comboBoxArduinoPort.Items.Count > 1)
            {
                comboBoxArduinoPort.SelectedIndex = 1;
            }
        }

        private void buttonSelectPath_Click(object sender, EventArgs e)
        {
            saveFileDialog.Filter = "SEM file (*.txt)|*.TXT";
            if (DialogResult.OK == saveFileDialog.ShowDialog())
            {
                textBoxLogfilePath.Text = saveFileDialog.FileName;
            }
        }

        private void buttonSelectPathErrorLogging_Click(object sender, EventArgs e)
        {
            saveFileDialog.Filter = "TXT file (*.txt)|*.txt";
            if (DialogResult.OK == saveFileDialog.ShowDialog())
            {
                textBoxLogfilePathError.Text = saveFileDialog.FileName;
            }
        }

        private void buttonArduinoRefresh_Click(object sender, EventArgs e)
        {
            comboBoxArduinoPort.Items.Clear();
            foreach (string portname in SerialPort.GetPortNames())
            {
                comboBoxArduinoPort.Items.Add(portname);
                comboBoxArduinoPort.SelectedIndex = 0;
            }
        }

        private void buttonXBeeRefresh_Click(object sender, EventArgs e)
        {
            comboBoxXBeePort.Items.Clear();
            foreach (string portname in SerialPort.GetPortNames())
            {
                comboBoxXBeePort.Items.Add(portname);
                comboBoxXBeePort.SelectedIndex = 0;
            }
        }

        private void buttonSaveSettings_Click(object sender, EventArgs e)
        {
            buttonApply_Click(null, EventArgs.Empty);
            saveFileDialog.Filter = "SEM file (*.SEM)|*.SEM";
            if (DialogResult.OK == saveFileDialog.ShowDialog())
            {
                string savePath = saveFileDialog.FileName;
                ConfigSingleton.Export<ConfigSingleton>(ConfigSingleton.Settings, savePath);
            }
        }

        private void buttonApply_Click(object sender, EventArgs e)
        {
            _errorList = new List<string>();

            arduinoApply();
            globalApply();
            logfileApply();
            sensorApply();
            xBeeApply();

            if (_errorList.Count > 0)
            {
                string errorMessage = "";
                foreach (string error in _errorList)
                {
                    errorMessage += error;
                }
                MessageBox.Show(errorMessage);
            }
            else
            {
                if (sender is Button)
                {
                    MessageBox.Show("Succesfully applied!");
                }
                readyToBeInitiated = true;
            }
        }
        #endregion

        private void ConfigurationForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            buttonApply_Click(null, EventArgs.Empty);
            if (_errorList.Count > 0)
            {
                DialogResult result = MessageBox.Show("Not all values have been set, do you still want to quit?", "Quit?", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if (result == DialogResult.No)
                {
                    e.Cancel = true;
                }
            }
        }

        private void ConfigurationForm_Load(object sender, EventArgs e)
        {
            
        }

        private string getComPort(string portDescription)
        {
            string result = null;

            try
            {
                ManagementObjectSearcher searcher =
                     new ManagementObjectSearcher("root\\CIMV2",
                         "SELECT Caption FROM Win32_PnPEntity");

                foreach (ManagementObject queryObj in searcher.Get())
                {
                    string queryItemString = queryObj["Caption"].ToString();
                    if (queryItemString.Contains("(COM"))
                    {
                        // help splitter
                        string[] splitArray = queryItemString.Split(new string[] { " (" }, StringSplitOptions.None);
                        // subtract the port description
                        string queryItemDescription = splitArray[0];

                        if (queryItemDescription == portDescription)
                        {
                            // subtract the port name
                            string portName = splitArray[1].Split(')')[0];

                            result = portName;
                        }
                    }
                }
            }
            catch (ManagementException)
            {
                return null;
            }
            return result;
        }
    }
}
